/*
    Copyright 2006-2011 Patrik Jonsson, sunrise@familjenjonsson.org

    This file is part of Sunrise.

    Sunrise is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    Sunrise is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Sunrise.  If not, see <http://www.gnu.org/licenses/>.

*/

/** \file 

    Contains declaration of the grain_model abstract class and the
    GrainModelFactory. */

#ifndef __grain_model__
#define __grain_model__

#include "boost/shared_ptr.hpp"
#include "boost/function.hpp"
#include "Factory.h"
#include "Singleton.h"
#include "mcrx.h"
#include "preferences.h"
#include "misc.h"
#include "mcrx-units.h"
#include "chromatic_policy.h"
#include "scatterer.h"
#include "vecops.h"

namespace mcrx {
  template <template<class> class, typename> class grain_model;
  
  template <template<class> class chromatic_policy, typename T_rng_policy> 
  boost::shared_ptr<mcrx::grain_model<chromatic_policy, T_rng_policy> >
  load_grain_model(Preferences& p, const T_unit_map& units);
  
  /** This typedef is for supporting the creation of different grain_model
      classes depending on configuration. */
  typedef boost::shared_ptr<grain_model<polychromatic_scatterer_policy, 
					mcrx_rng_policy> > 
    GrainModelFactoryReturntype;
  /** This typedef is for supporting the creation of different grain_model
      classes depending on configuration. */
  typedef boost::function<GrainModelFactoryReturntype(const Preferences&)> 
    GrainModelFactoryFunctor;

  /** The grain model factory is responsible for creating different
      classes of the grain_model hierarchy based on a string
      identifier, which is given by the grain_model configuration
      keyword. It is based on the Factory class from the Loki
      library.  */
  typedef Loki::SingletonHolder<Loki::Factory<GrainModelFactoryReturntype,
					      std::string,
					      const Preferences&,
					      GrainModelFactoryFunctor> > 
    GrainModelFactory;
};


/** A grain model is a complete dust grain description, in general
    consisting of several grain species with distinct size
    distributions.  It is also an abstract base class for classes that
    can calculate grain temperatures and the total infrared SED
    emitted from a region with a certain radiation intensity and dust
    mass. It is also a subclass of simple_HG_dust_grain, so can be
    used as a scatterer during ray tracing.  */
template <template<class> class chromatic_policy, 
	  typename T_rng_policy = mcrx::global_random> 
class mcrx::grain_model :
  public mcrx::simple_HG_dust_grain<chromatic_policy, T_rng_policy> {
protected:

  /** The grain_model units. The reason this is in this abstract base
      class is because the actual grain model classes have two sets of
      units resulting from their schizophrenic nature of being both
      grain_model and scatterer types. *These* units refer to their
      grain model characteristics, while the units of the scatterer
      base class refer to the units of their opacity curves. Messy,
      but that's the way it is. */
  T_unit_map units_;

public:
  /** Virtual constructor. */
  virtual boost::shared_ptr<grain_model> clone() const=0;

  /** The basic function for the grain model, which is to calculate
      the emission SED of the grains subjected to a certain radiative
      intensity. The input data are the intensities in the grid cells
      as a 2D array of (cell, lambda), which can be supplied as an
      expression template, and the dust masses in the cells as a 1D
      array. The SED is ADDED to the supplied 2D (cell, lambda)
      array. */
  template<typename T>
  void calculate_SED(const blitz::ETBase<T>& intensity, const array_1& m_dust,
		     array_2& sed, array_2* temp=0) const;

  /** Resample the grain model data onto the specified wavelength
      grid. Optionally a separate wavelength grid used for emission
      calculations can be specified.  */
  virtual void resample (const array_1& lambda, 
			 const array_1& elambda=array_1())=0;
  /** The wavelength grid (for heating, if split grids are used). */
  virtual const array_1& alambda() const= 0;
  /** The emission wavelength grid (if split grids are used). */
  virtual const array_1& elambda() const= 0;
  /** Return the units the grain model uses to calculate the
      SED. (Note that these are in general different from the units of
      the extinction curve.) */
  const T_unit_map& units() const {return units_;};
};


/** This function takes care of calling the grain model factory with
    the correct model and parameters to create the actual grain model
    object. */
template <template<class> class chromatic_policy, typename T_rng_policy> 
boost::shared_ptr<mcrx::grain_model<chromatic_policy, T_rng_policy> >
mcrx::load_grain_model(Preferences& p, const T_unit_map& units)
{
  using namespace std;
  const string model = p.getValue("grain_model", string ());
  const string sigma_dir = word_expand(p.getValue("grain_data_directory", string ()))[0];
  
  const T_float min_lambda = p.defined("grain_min_emission_wavelength") ? 
    p.getValue("grain_min_emission_wavelength", T_float ()) : 0;

  // THIS IS A HACK BECAUSE WE NEED TO GET UNITS TO GRAIN MODELS
  Preferences tempp(p);
  // assert(p.getValue("massunit",string())==units.get("mass"));
  // assert(p.getValue("lengthunit",string())==units.get("length"));
  // assert(p.getValue("wavelengthunit",string())==units.get("wavelength"));

  //tempp.setValue("massunit", units.get("mass"));
  //tempp.setValue("lengthunit", units.get("length"));
  //tempp.setValue("wavelengthunit", units.get("wavelength"));
  p.setValue("massunit", units.get("mass"));
  p.setValue("lengthunit", units.get("length"));
  p.setValue("wavelengthunit", units.get("wavelength"));

  cout << "Using dust data from grain model: " << model << endl;
  
  //return GrainModelFactory::Instance().CreateObject(model,tempp);
  return GrainModelFactory::Instance().CreateObject(model,p);
}


#endif
